Skip to content

Comments

Implement wire protocol version compatibility semantics#113

Open
conradbzura wants to merge 7 commits intomainfrom
protobuf-version-handshake
Open

Implement wire protocol version compatibility semantics#113
conradbzura wants to merge 7 commits intomainfrom
protobuf-version-handshake

Conversation

@conradbzura
Copy link
Contributor

@conradbzura conradbzura commented Feb 21, 2026

Summary

Add protocol version awareness to Wool's wire protocol. Incompatible workers (different major version) are filtered out during discovery before they ever reach the load balancer. As a secondary defense, a gRPC server interceptor extracts the client version from raw request bytes and rejects requests with empty, unparseable, or incompatible major versions via Nack.

A new README for the protobuf subpackage documents the dispatch wire protocol, serialization strategy, schema evolution rules, and version compatibility semantics.

Closes #102

Proposed changes

Proto schema changes

Add a version string field (field 1) to Task in task.proto, reserving it for pre-deserialization version extraction. Add TaskVersionEnvelope — a minimal message containing only string version = 1 — for the interceptor to parse field 1 from any wire format. Add a version string field to Ack in worker.proto for observability.

Task version population

Populate Task.version with wool.__version__ in to_protobuf(). Populate Ack(version=wool.__version__) when acknowledging a task. Handle Nack responses in WorkerConnection.dispatch() by raising RpcError.

Discovery-time version filtering

Add parse_major_version helper and _create_version_filter to WorkerProxy. Compose security and version filters into a single compatible predicate used across all discovery modes (pool URI, static workers).

Dispatch-time version interception

Add VersionInterceptor — a gRPC server interceptor that extracts the client version from raw request bytes via TaskVersionEnvelope before full deserialization. Reject requests with empty, unparseable, or incompatible major versions with a Nack. Wire the interceptor into WorkerProcess.

Protobuf subpackage README

Add wool/src/wool/runtime/protobuf/README.md documenting the dispatch wire protocol, serialization strategy, version compatibility guarantees, and schema evolution rules — written for the Wool user persona.

Test cases

Test Suite Test ID Given When Then Coverage Target
TestWorkerProxy VP-001 A proxy with version X.a.b and a worker with version X.c.d The worker is discovered via static workers list Worker is accepted into the load balancer context Same major version passes filter
TestWorkerProxy VP-002 A proxy with major version X and a worker with different major version Y The worker is discovered via static workers list Worker is rejected from the load balancer context Different major version filtered
TestWorkerProxy VP-003 A proxy with a valid version and a worker with no parseable integer prefix The worker is discovered via static workers list Worker is rejected from the load balancer context Unparseable version filtered
TestWorkerProxy VP-004 A proxy with credentials and version 1.x A secure worker with version 2.y is discovered Worker is rejected (version filter applies alongside security filter) Combined filter interaction
TestWorkerService VP-005 A running WorkerService A task is dispatched with a compatible version Ack response contains wool.__version__ Ack version population
TestWorkerService VP-006 A running WorkerService with the version interceptor A task with empty version field is dispatched Worker responds with a Nack citing unparseable version Empty version rejection
TestWorkerService VP-007 Two semver-like version strings with different major versions A task is dispatched through the version interceptor Dispatch yields a Nack citing version mismatch Major version mismatch Nack
TestWorkerService VP-008 A running WorkerService with the version interceptor A task with unparseable version string is dispatched Worker responds with a Nack citing unparseable version Unparseable version rejection
TestWorkerConnection VP-009 A mock worker returning Ack with version field dispatch() is called Ack is accepted and stream is returned normally Ack version field passthrough
TestWorkerConnection VP-010 A mock worker returning Nack with version mismatch reason dispatch() is called RpcError is raised with the rejection reason Nack response error handling
TestTask VP-011 A Task instance to_protobuf() is called Protobuf Task contains wool.__version__ in version field Task version serialization
TestTask VP-012 Any PEP 440-like version string A protobuf Task with that version is serialized and parsed back Version field is preserved on the wire Version field round-trip

Implementation plan

    • Add version field to Task and TaskVersionEnvelope in task.proto; add version field to Ack in worker.proto; re-export TaskVersionEnvelope in wrapper
    • Populate Task.version with wool.__version__ in to_protobuf(); populate Ack.version in WorkerService.dispatch(); handle Nack in WorkerConnection.dispatch()
    • Add parse_major_version helper and _create_version_filter to WorkerProxy; compose security and version filters into compatible predicate
    • Add VersionInterceptor with strict version checking (reject empty, unparseable, incompatible); wire into WorkerProcess
    • Add version compatibility tests across test_proxy.py, test_service.py, test_connection.py, and test_task.py (VP-001 through VP-012)
    • Add protobuf subpackage README documenting wire protocol, serialization, version compatibility, and schema evolution rules

@conradbzura conradbzura added documentation Indicates that a PR includes changes to documentation feature New feature or capability labels Feb 21, 2026
@conradbzura conradbzura force-pushed the protobuf-version-handshake branch from 12815ea to c4b4b25 Compare February 21, 2026 18:43
@wool-labs wool-labs bot added the code-change Indicates that a PR should trigger a release label Feb 21, 2026
local_major,
client_major,
):
"""Test dispatch yields Nack for incompatible major version.
Copy link
Contributor Author

@conradbzura conradbzura Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or empty or unparsable version.

@conradbzura conradbzura force-pushed the protobuf-version-handshake branch from c4b4b25 to f498d1a Compare February 22, 2026 15:52
|<──────── Response(Result) ────| (one or more results)
|<──────── Response(Exception) ─| (on failure)
| |
```
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a simple Mermaid sequence diagram.

Reserve field 1 for `version` in the Task message and add
TaskVersionEnvelope for pre-deserialization version extraction.
Add a `version` field to Ack for observability.
Stamp wool.__version__ on outgoing Task protobuf messages and on
Ack responses. Raise RpcError in WorkerConnection when a Nack
is received instead of an Ack.
Add parse_major_version helper and _create_version_filter to
WorkerProxy. Compose security and version filters into a single
compatible predicate used across all discovery modes.
Extract the client version from raw request bytes via
TaskVersionEnvelope before full deserialization. Reject requests
with empty, unparseable, or incompatible major versions with a
Nack.
Cover version round-tripping through protobuf, Nack handling in
WorkerConnection, discovery-time major version filtering in
WorkerProxy, and dispatch-time version interception in
WorkerService (including empty, unparseable, and incompatible
version scenarios).
Document the wire protocol, dispatch sequence, serialization
strategy, version compatibility guarantees, and schema evolution
rules for users of Wool.
@conradbzura conradbzura force-pushed the protobuf-version-handshake branch from 3f765b6 to 061dea9 Compare February 22, 2026 16:13
@conradbzura conradbzura marked this pull request as ready for review February 22, 2026 16:14
@conradbzura conradbzura force-pushed the protobuf-version-handshake branch 2 times, most recently from bb556f3 to de0cdec Compare February 22, 2026 17:04
The default shallow clone fetches no tags, causing the build-hooks
git version parser to fall back to "0+<hash>". This version string
is unparseable by parse_major_version, failing all test_service.py
tests in CI. Adding "fetch-tags: true" resolves a real tag without
requiring a full history clone.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code-change Indicates that a PR should trigger a release documentation Indicates that a PR includes changes to documentation feature New feature or capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement wire protocol version compatibility semantics

1 participant